use std::ffi::c_void;

use android_logger::Config;
use anyhow::Result;
use jni::{
    objects::{JObject, JString, JValue},
    sys::{self, jint, jobject, JNI_VERSION_1_6},
    JNIEnv, JavaVM,
};
use libusb1_sys::libusb_set_option;
use log::LevelFilter;
use probe_rs::{
    flashing::{download_file_with_options, DownloadOptions, FlashProgress, Format, ProgressEvent}, open_by_fd, open_by_fd_ep, Permissions, Session
};
use rusb::constants::LIBUSB_OPTION_NO_DEVICE_DISCOVERY;

#[no_mangle]
pub extern "system" fn JNI_OnLoad(_vm: JavaVM, _v: *mut c_void) -> jint {
    let cfg = Config::default()
        .with_max_level(LevelFilter::Debug)
        .with_tag("native log");
    android_logger::init_once(cfg);
    unsafe {
        libusb_set_option(std::ptr::null_mut(), LIBUSB_OPTION_NO_DEVICE_DISCOVERY, 0);
    }
    JNI_VERSION_1_6
}

#[no_mangle]
pub extern "system" fn JNI_OnUnload(_vm: JavaVM, _v: *mut c_void) {
    // unregister_device();
}

#[no_mangle]
pub extern "system" fn Java_com_hontech_dapapp_MainActivity_00024Companion_usbInfo(
    _env: JNIEnv,
    _obj: JObject,
    _fd: jint,
) {

}

const IO_EXCEPTION_PATH: &'static str = "java/io/IOException";
const PROG_START: &'static str = "(J)V";
const PROG_PROGRAM: &'static str = "(II)V";
const PROG_ERASED: &'static str = "(JI)V";

fn on_start(env: &mut JNIEnv, cb: &JObject, len: u64) {
    let ret = env.call_method(cb, "onStart", PROG_START, &[JValue::Long(len as i64)]);
    if let Err(e) = ret {
        log::error!("err:{}", e);
    }
}

fn on_program(env: &mut JNIEnv, cb: &JObject, size: u32, time: u32) {
    let ret = env.call_method(
        cb,
        "onProgram",
        PROG_PROGRAM,
        &[JValue::Int(size as i32), JValue::Int(time as i32)],
    );
    if let Err(e) = ret {
        log::error!("err:{}", e);
    }
}

fn on_erased(env: &mut JNIEnv, cb: &JObject, size: u64, time: u32) {
    let ret = env.call_method(
        cb,
        "onErased",
        PROG_ERASED,
        &[JValue::Long(size as i64), JValue::Int(time as i32)],
    );
    if let Err(e) = ret {
        log::error!("err:{}", e);
    }
}

fn throw(env: &mut JNIEnv, e: anyhow::Error) {
    _ = env.throw_new(IO_EXCEPTION_PATH, e.to_string());
}

fn download_impl(raw_env: *mut sys::JNIEnv, session: &mut Session, path: &str, cb: jobject) -> Result<()> {

    let mut opt = DownloadOptions::default();
    opt.verify = true;
    opt.progress = Some(FlashProgress::new(move |event| {
        match event {
            ProgressEvent::StartedProgramming { length } => {
              
                let mut env = unsafe { JNIEnv::from_raw(raw_env).unwrap() };
                let cb = unsafe { JObject::from_raw(cb) };
                on_start(&mut env, &cb, length);
            }

            ProgressEvent::SectorErased { size, time } => {
              
                let mut env = unsafe { JNIEnv::from_raw(raw_env).unwrap() };
                let cb = unsafe { JObject::from_raw(cb) };
                on_erased(&mut env, &cb, size, time.as_millis() as u32);
            }

            ProgressEvent::PageProgrammed { size, time } => {
              
                let mut env = unsafe { JNIEnv::from_raw(raw_env).unwrap() };
                let cb = unsafe { JObject::from_raw(cb) };
                on_program(&mut env, &cb, size, time.as_millis() as u32);
            }
            _ => {}
        };
    }));

    download_file_with_options(session, path, Format::Hex, opt)?;

    let cores = session.list_cores();
    for info in cores {
        let mut core = session.core(info.0)?;
        core.reset()?;
    }

    Ok(())
}

fn download(env: &mut JNIEnv, fd: jint, target: JString, path: JString, cb: jobject) -> Result<()> {
    let target = unsafe { env.get_string_unchecked(&target)? };
    let target = target.to_str()?;
    let path = unsafe { env.get_string_unchecked(&path)? };
    let path = path.to_str()?;

    let probe = open_by_fd(fd)?;
    let mut session = probe.attach(target, Permissions::default())?;

    let raw_env = env.get_raw();

    download_impl(raw_env, &mut session, path, cb)?;

    Ok(())
}

#[no_mangle]
pub extern "system" fn Java_com_hontech_dapapp_DapLink_download(
    mut env: JNIEnv,
    _obj: JObject,
    fd: jint,
    target: JString,
    path: JString,
    cb: JObject,
) {
    let ret = download(&mut env, fd, target, path, cb.as_raw());
    if let Err(e) = ret {
        throw(&mut env, e);
    }
}

fn download2(env: &mut JNIEnv, fd: jint, ep_in: jint, ep_out: jint, max_packet_size: jint, target: JString, path: JString, cb: JObject) -> Result<()> {
    let prob = open_by_fd_ep(fd, ep_out as u8, ep_in as u8, max_packet_size as usize)?;
    let target = unsafe { env.get_string_unchecked(&target)? };
    let target = target.to_str()?;
    let path = unsafe { env.get_string_unchecked(&path)? };
    let path = path.to_str()?;
    let mut session = prob.attach(target, Permissions::default())?;
    download_impl(env.get_raw(), &mut session, path, cb.as_raw())?;
    Ok(())
}

#[no_mangle]
pub extern "system" fn Java_com_hontech_dapapp_DapLink_downloadWithEp(
    mut env: JNIEnv,
    _: JObject,
    fd: jint,
    ep_in: jint,
    ep_out: jint,
    max_packet_size: jint,
    target: JString,
    path: JString,
    cb: JObject
) {
    if let Err(e) = download2(&mut env, fd, ep_in, ep_out, max_packet_size, target, path, cb) {
        throw(&mut env, e);
    }
}

